/* uMP3.cpp

  uMP3 Interface Library (Play Commands)

  A library to communicate with the uMP3 Playback Module
  from Rogue Robotics (http://www.roguerobotics.com/).
  Access to the playback commands
  Requires latest firmware (i.e. > 111.08.b001)
  See http://www.roguerobotics.com/faq/update_firmware for updating firmware.

  Written by Brett Hagman

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

Version Modified By Date     Comments
------- ----------- -------- --------
0001    B Hagman    09/07/05 Initial coding

*************************************************/

#include <stdint.h>
#include <wiring.h>
#include "uMP3.h"

/*************************************************
* Private Constants
*************************************************/

#define UMP3_MIN_FW_VERSION_FOR_NEW_COMMANDS 11101

#define UMP3_FADE_STEPS                 40
#define UMP3_FADE_AUDIBLE_DIFF          5
#define UMP3_FADE_DEFAULT_TIME          1000

#define UMP3_STATS_SIZE                 0
#define UMP3_STATS_POSITION             1
#define UMP3_STATS_REMAINING            2

/*************************************************
* Constructor
*************************************************/

/*
uMP3::uMP3(int8_t (*_af)(void), int (*_pf)(void), int (*_rf)(void), void (*_wf)(uint8_t))
: LastErrorCode(0),
  _promptchar(UMP3_DEFAULT_PROMPT),
  _fwversion(0)
{
  _availablef = _af;
  _peekf = _pf;
  _readf = _rf;
  _writef = _wf;
}
*/

uMP3::uMP3(SerialBase &comms)
: LastErrorCode(0),
  _promptchar(UMP3_DEFAULT_PROMPT),
  _fwversion(0)
{
  _comms = &comms;
}


/*************************************************
* Public Methods
*************************************************/

int8_t uMP3::sync(void)
{
  // procedure:
  // 1. sync (send ESC, clear prompt)
  // 2a. get prompt ("st p")
  // 2b. get version ("v")
  // 3. check status
  uint8_t r;

  // 0. empty any data in the serial buffer
  _flush();

  // 1. sync
  _comms->write(0x1b);                  // send ESC to clear buffer on uMP3
  _read_blocked();                      // consume prompt


  // 2a. get prompt
  print('S'); print('T'); print('P'); print('\r');  // get our prompt (if possible)
  while(!_comms->available());
  r = _comms->peek();
  if(r != 'E')
  {
    _promptchar = _getnumber(10);
    _read_blocked();                    // consume prompt
  }
  else
  {
    _promptchar = UMP3_DEFAULT_PROMPT;  // this is an older version, no prompt char setting
    _get_response();                    // get our error
  }

  // 2b. get version
  
  _get_version();

  if(_fwversion < UMP3_MIN_FW_VERSION_FOR_NEW_COMMANDS)
  {
    // we need to set the write timeout to allow us to control when a line is finished
    // in writeln.
    changesetting('T', 1);    // 10 ms timeout
  }
  
  // 3. check status

  print('F'); print('C'); print('Z'); print('\r'); // Get status
  
  if(_get_response())
    return -1;
  else
  {
    // good
    _read_blocked();                    // consume prompt

    return 0;
  }
}


int8_t uMP3::changesetting(char setting, uint8_t value)
{
  print('S'); print('T'); print(setting); print(value, DEC); print('\r');
  
  return _get_response();
}


int8_t uMP3::changesetting(char setting, const char* value)
{
  print('S'); print('T'); print(setting); print(value); print('\r');
  
  return _get_response();
}

int16_t uMP3::getsetting(char setting)
{
  uint8_t value;
  
  print('S'); print('T'); print(setting); print('\r');
  
  while(!_comms->available());
  if(_comms->peek() != 'E')
  {
    value = _getnumber(10);
    _read_blocked();                    // consume prompt
  }
  else
  {
    value = _get_response();            // get the error
  }
  
  return value;
}


int8_t uMP3::playfile(const char *filename)
{
  print('P'); print('C'); print('F');
  if(_fwversion < UMP3_MIN_FW_VERSION_FOR_NEW_COMMANDS)
    if(*filename != '/')
      print('/');

  print(filename);
  print('\r');
  
  return _get_response();
}


uint16_t uMP3::getvolume(void)
{
  uint16_t l, r;

  print('P'); print('C'); print('V'); print('\r');

  l = _getnumber(10);
  _read_blocked();                      // consume separator
  r = _getnumber(10);

  _read_blocked();                      // consume prompt
  
  return l << 8 | r;
}


void uMP3::setvolume(uint8_t newvolume)
{
  print('P'); print('C'); print('V');
  print(newvolume, DEC);
  print('\r');
  
  _read_blocked();                      // consume prompt
}


void uMP3::setvolume(uint8_t new_vleft, uint8_t new_vright)
{
  print('P'); print('C'); print('V');
  print(new_vleft, DEC);
  print(' ');
  print(new_vright, DEC);
  print('\r');

  _read_blocked();                      // consume prompt
}



void uMP3::fade(uint8_t newvolume)
{
  fade_lr(newvolume, newvolume, UMP3_FADE_DEFAULT_TIME);
}



void uMP3::fade(uint8_t newvolume, uint16_t fadespeed)
{
  fade_lr(newvolume, newvolume, fadespeed);
}



void uMP3::fade_lr(uint8_t new_vleft, uint8_t new_vright)
{
  fade_lr(new_vleft, new_vright, UMP3_FADE_DEFAULT_TIME);
}



void uMP3::fade_lr(uint8_t new_vleft, uint8_t new_vright, uint16_t fadespeed)
{
  // fades either/both channels to new volume in fadespeed milliseconds
  // always 20 steps
  uint16_t vleft, vright;
  uint16_t currentvolume;
  uint16_t fadetimestep = 0;
  int16_t il, ir;
  int8_t i;

  fadetimestep = fadespeed/UMP3_FADE_STEPS;

  if(fadetimestep<UMP3_FADE_AUDIBLE_DIFF)
  {
    // too fast to hear - just set the volume
    setvolume(new_vleft, new_vright);
  }
  else
  {
    currentvolume = getvolume();
    // for precision, we move the volume over by 4 bits
    vleft = ((currentvolume >> 8) & 0xff) * 16;
    vright = (currentvolume & 0xff) * 16;

    il = (((uint16_t)new_vleft)*16 - vleft);
    ir = (((uint16_t)new_vright)*16 - vright);

    il /= UMP3_FADE_STEPS;
    ir /= UMP3_FADE_STEPS;

    for(i = 0; i < UMP3_FADE_STEPS; i++)
    {
      vleft += il;
      vright += ir;
      setvolume(vleft/16, vright/16);
      delay(fadetimestep);
    }
  }
}



void uMP3::playpause(void)
{
  print('P'); print('C'); print('P'); print('\r');

  _read_blocked();                      // consume prompt
}



void uMP3::stop(void)
{
  print('P'); print('C'); print('S'); print('\r');

  _read_blocked();                      // consume prompt
}
    


uint16_t uMP3::getplaybackinfo(infotype info)
{
  uint16_t value = 0;
  uint8_t i;

  print('P'); print('C'); print('I'); print('\r');
  // now get the info we need
  
  for(i = 0; i < 3; i++)
  {
    if(i == info)
      value = _getnumber(10);
    else
      _getnumber(10);

    _read_blocked();                    // consume separator
  }
  
  if(info == CHANNELS)
    value = _read_blocked();            // get 'S', 'M', 'J'
  else
    _read_blocked();

  _read_blocked();                      // consume prompt
  
  return value;
}


char uMP3::getplaybackstatus(void)
{
  char value;

  print('P'); print('C'); print('Z'); print('\r');

  value = _read_blocked();

  while(_read_blocked() != _promptchar);
  
  return value;
}


void uMP3::jump(uint16_t newtime)
{
  print('P'); print('C'); print('J'); print(newtime, DEC); print('\r');

  _read_blocked();                      // consume prompt
}

void uMP3::setboost(uint8_t bass_amp, uint8_t bass_freq, int8_t treble_amp, uint8_t treble_freq)
{
  uint16_t newBoostRegister = 0;

  if (treble_freq > 15) treble_freq = 15;
  if (treble_amp < -8) treble_freq = -8;
  else if (treble_amp > 7) treble_freq = 7;
  if (bass_freq == 1) bass_freq = 0;
  else if (bass_freq > 15) bass_freq = 15;
  if (bass_amp > 15) bass_amp = 15;

  newBoostRegister = (uint8_t)treble_amp << 12;
  newBoostRegister |= treble_freq << 8;
  newBoostRegister |= bass_amp << 4;
  newBoostRegister |= bass_freq;

  setboost(newBoostRegister);
}

void uMP3::setboost(uint16_t newboost)
{
  print('P'); print('C'); print('B'); print(newboost, DEC); print('\r');

  _read_blocked();                      // consume prompt
}


void uMP3::setloop(uint8_t loopcount)
{
  print('P'); print('C'); print('O'); print(loopcount, DEC); print('\r');

  _read_blocked();                      // consume prompt
}




/*************************************************
* Public (virtual)
*************************************************/

void uMP3::write(uint8_t c)
{
  _comms->write(c);
} 


/*************************************************
* Private Methods
*************************************************/

void uMP3::_flush(void)
{
  while(_comms->available())
    _comms->read();
}


int8_t uMP3::_read_blocked(void)
{
  // int8_t r;
  
  while(!_comms->available());
  // while((r = this->_readf()) < 0);   // this would be faster if we could guarantee that the _readf() function
                                        // would return -1 if there was no byte read
  return _comms->read();
}


int8_t uMP3::_get_response(void)
{
  // looking for a response
  // If we get a space " ", we return as good and the remaining data can be retrieved
  // " ", ">", "Exx>" types only
  uint8_t r;
  uint8_t resp = 0;

  // we will return 0 if all is good, error code otherwise

  r = _read_blocked();

  if(r == ' ' || r == _promptchar)
    resp = 0;

  else if(r == 'E')
  {
    LastErrorCode = _getnumber(16);     // get our error code
    _read_blocked();                    // consume prompt
    
    resp = -1;
  }
  
  else
  {
    LastErrorCode = 0xFF;               // something got messed up, a resync would be nice
    resp = -1;
  }
  
  return resp;
}


int16_t uMP3::_get_version(void)
{
  // return the version of the installed uMP3
  print('V'); print('\r');
  
  // get first portion mmm.nn
  _fwversion = _getnumber(10);
  _read_blocked();                      // consume '.'
  _fwversion *= 100;
  _fwversion += _getnumber(10);
  
  while(_read_blocked() != _promptchar);  // consume up to prompt
  
  return _fwversion;
}


uint32_t uMP3::_get_filestats(int8_t handle, uint8_t valuetoget)
{
  uint32_t value = 0;

  print('I'); write('0'+handle); print('\r');
  
  while(!_comms->available());
  if(_comms->peek() != 'E')
  {
    if(valuetoget != UMP3_STATS_SIZE)
      value = _getnumber(10);           // current file position (use for either position, or remaining)

    _read_blocked();                    // consume '/' or ' '

    if(valuetoget == UMP3_STATS_SIZE)
      value = _getnumber(10);           // file size
    else if(valuetoget == UMP3_STATS_REMAINING)
      value = _getnumber(10) - value;   // calculate bytes remaining (value assigned above)

    _read_blocked();                    // consume prompt
  }
  // else return 0, error code is actually in .LastErrorCode
  
  return value;
}


// if SerialComm accepted, then this is part of that base class
int32_t uMP3::_getnumber(uint8_t base)
{
	uint8_t c, neg = 0;
	uint32_t val;

	val = 0;
	while(!_comms->available());
  c = _comms->peek();
  
  if(c == '-')
  {
    neg = 1;
    _comms->read();  // remove
    while(!_comms->available());
    c = _comms->peek();
  }
  
	while(((c >= 'A') && (c <= 'Z'))
	    || ((c >= 'a') && (c <= 'z'))
	    || ((c >= '0') && (c <= '9')))
	{
		if(c >= 'a') c -= 0x57;             // c = c - 'a' + 0x0a, c = c - ('a' - 0x0a)
		else if(c >= 'A') c -= 0x37;        // c = c - 'A' + 0x0A
		else c -= '0';
		if(c >= base) break;

		val *= base;
		val += c;
		_comms->read();                     // take the byte from the queue
		while(!_comms->available());        // wait for the next byte
		c = _comms->peek();
	}
	return neg ? -val : val;
}
